﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using MyFramework.Command.Interfaces;

namespace MyFramework.Command.DelegateWatchCommand
{
    /// <summary>
    /// Watch collection
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class WatchCollection<T> : IPropertyWatchTail
    {
        #region Fields

        /// <summary>
        /// The pattern
        /// </summary>
        private readonly PropertyWatchChain pattern;

        /// <summary>
        /// The next
        /// </summary>
        private List<PropertyWatchChain> next;

        /// <summary>
        /// The has successors
        /// </summary>
        private bool hasSuccessors;

        /// <summary>
        /// The source
        /// </summary>
        private INotifyPropertyChanged source;

        /// <summary>
        /// The collection
        /// </summary>
        private ObservableCollection<T> collection;

        #endregion

        #region constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="WatchCollection&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="pattern">The pattern.</param>
        public WatchCollection(PropertyWatchChain pattern)
        {
            this.pattern = pattern;
            hasSuccessors = pattern != null;
            if (hasSuccessors && !typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T)))
                throw new ArgumentException("WatchCollection must have pattern==null if the elements of the watched collection dont implement INotifyPropertyChanged");
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public INotifyPropertyChanged Source
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    if (collection != null)
                    {
                        collection.CollectionChanged -= new NotifyCollectionChangedEventHandler(source_CollectionChanged);
                    }

                    source = value;

                    if (source != null)
                    {
                        collection = value as ObservableCollection<T>;
                        if (collection == null)
                            throw new ArgumentOutOfRangeException(
                                string.Format("value must be ObservableCollection<{0}>", typeof(T).Name));
                        collection.CollectionChanged += new NotifyCollectionChangedEventHandler(source_CollectionChanged);
                        this.CreateSuccessors();
                    }
                    else
                        next = null;
                    this.OnWatchedPropertyChanged();
                }
            }
        }

        /// <summary>
        /// Gets or sets the parent.
        /// </summary>
        /// <value>
        /// The parent.
        /// </value>
        public PropertyWatchChain Parent { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// Handles the CollectionChanged event of the source control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        private void source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (hasSuccessors)
            {
                if (e.Action == NotifyCollectionChangedAction.Reset)
                {
                    this.CreateSuccessors();
                }
                else
                {
                    if (e.OldItems != null)
                    {
                        foreach (INotifyPropertyChanged x in e.OldItems)
                        {
                            var y = next.SingleOrDefault(z => object.ReferenceEquals(z.Head.Source, x));
                            if (y != null)
                            {
                                next.Remove(y);
                                y.WatchedPropertyChanged -= new EventHandler<EventArgs>(next_WatchedPropertyChanged);
                            }
                        }
                    }
                    if (e.NewItems != null)
                    {
                        foreach (INotifyPropertyChanged x in e.NewItems)
                        {
                            var y = pattern.Clone();
                            y.Head.Source = x;
                            next.Add(y);
                            y.WatchedPropertyChanged += new EventHandler<EventArgs>(next_WatchedPropertyChanged);
                        }
                    }
                }
            }
            this.OnWatchedPropertyChanged();
        }

        /// <summary>
        /// Creates the successors.
        /// </summary>
        private void CreateSuccessors()
        {
            if (!hasSuccessors)
                return; // --------------->>>>>>>>>>>>>>>>>>>>>>>

            // Unhook successors from WatchedPropertyChanged
            if (next != null)
            {
                foreach (var y in next)
                    y.WatchedPropertyChanged -= new EventHandler<EventArgs>(next_WatchedPropertyChanged);
            }

            next = collection
                .Where(x => x != null)
                .Cast<INotifyPropertyChanged>()
                .Select(x =>
                {
                    var ret = pattern.Clone();
                    ret.Head.Source = x;
                    ret.WatchedPropertyChanged += new EventHandler<EventArgs>(next_WatchedPropertyChanged);
                    return ret;
                })
                .ToList();
        }

        /// <summary>
        /// Handles the WatchedPropertyChanged event of the next control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void next_WatchedPropertyChanged(object sender, EventArgs e)
        {
            this.OnWatchedPropertyChanged();
        }

        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns></returns>
        public IPropertyWatchTail Clone()
        {
            return new WatchCollection<T>(pattern);
        }

        /// <summary>
        /// Called when [watched property changed].
        /// </summary>
        private void OnWatchedPropertyChanged()
        {
            if (this.Parent != null)
                this.Parent.OnWatchedPropertyChanged();
        }

        /// <summary>
        /// Gets the tail.
        /// </summary>
        /// <returns></returns>
        public IPropertyWatchTail GetTail()
        {
            return this;
        }

        #endregion
    }
}
